""" from lightshowpi external input
You must have a complete, functioning lightshowpy installation and pyaudio installed for
this to function. (install pyaudio with sudo apt-get install python-pyaudio)
Put this file in lightshowpi/py/
Get the USB adapter functioning first in raspbian before you try anything here.
Make sure you use something like alsamixer to turn up the gain and either try recording
something with arecord or audacity.
Also, make sure that the USB device is set as the default
Next run audiodevices.py and find the default input device. A Sabrent USB audio adapter
on my raspberry turned out to be device 3, yours may be different.

Then edit this file and set the line

device=1
to match your device number.

Once you have everything configured, start playing media on your external device, launch this
prog with
sudo python extern.py
and see what happens

You may need to edit sample rate. The Sabrent device used to create this code has 48khz input
but would throw IOERROR: IO Overflow after a few seconds at both 48 and 44khz.
It runs _ok_ with 22050 as sample rate, but you may need to change this to a different value
to make your system happy

The file, as-is will print out the channel and brightness values as it runs. If your lights are
not blinking, look at the calculated brightness being printed in the term window. If you see
values lower than 0.5, you need to either edit the code to lower the turn on threshold, or
increase the input signal or increase the gain (or some combination of the three.)
Conversely if the lights turn on and don't blink, you need to lower the input signal, or decrease
the gain.
Once you have it working and no longer want to see the brightness data, comment out the line:
print data

Consider this code to not be beta, nor alpha release but experimental.
It may require a quite a bit of work on your part to get functional

# Author: Todd Giles (todd@lightshowpi.com)
# Author: Chris Usey (chris.usey@gmail.com)
# Author: Ryan Jennings
# Author: Paul Dunn (dunnsept@gmail.com) 
"""


#TODO still TONS of cleanup/experimentation left. add-in things like calculate std & mean on-the-fly,etc
import pyaudio 
import sys
import math
import numpy as np
import configuration_manager as cm
import hardware_controller as hc

_CONFIG = cm.CONFIG
CHUNK_SIZE = 2048
GPIOLEN = 8
def piff(val, sample_rate):
    '''Return the power array index corresponding to a particular frequency.'''
    return int(CHUNK_SIZE * val / sample_rate)

# TODO(todd): Move FFT related code into separate file as a library
def calculate_levels(data, sample_rate, frequency_limits):
    '''Calculate frequency response for each channel

    Initial FFT code inspired from the code posted here:
    http://www.raspberrypi.org/phpBB3/viewtopic.php?t=35838&p=454041

    Optimizations from work by Scott Driscoll:
    http://www.instructables.com/id/Raspberry-Pi-Spectrum-Analyzer-with-RGB-LED-Strip-/
    '''

    # create a numpy array. This won't work with a mono file, stereo only.
    data_stereo = np.frombuffer(data, dtype=np.int16)
    data = np.empty(len(data) / 4)  # data has two channels and 2 bytes per channel
    data[:] = data_stereo[::2]  # pull out the even values, just using left channel

    # if you take an FFT of a chunk of audio, the edges will look like
    # super high frequency cutoffs. Applying a window tapers the edges
    # of each end of the chunk down to zero.
    window = np.hanning(len(data))
    data = data * window

    # Apply FFT - real data
    fourier = np.fft.rfft(data)

    # Remove last element in array to make it the same size as CHUNK_SIZE
    fourier = np.delete(fourier, len(fourier) - 1)

    # Calculate the power spectrum
    power = np.abs(fourier) ** 2

    matrix = [0 for i in range(GPIOLEN)]
    for i in range(GPIOLEN):
        # take the log10 of the resulting sum to approximate how human ears perceive sound levels
        matrix[i] = np.log10(np.sum(power[piff(frequency_limits[i][0], sample_rate)
                                          :piff(frequency_limits[i][1], sample_rate):1]))

    return matrix

def extern_show():
    chunk = 2048 
    sample_rate = 22050   #<----- you might need to change this one
    min_frequency = 20
    max_frequency = 15000
    _CUSTOM_CHANNEL_FREQUENCIES = 0
    _CUSTOM_CHANNEL_MAPPING = 0
    matrix    = [0,0,0,0,0,0,0,0]
#CHANGE THIS TO CORRECT INPUT DEVICE
# Enable stereo mixing in your sound card
# to make you sound output an input
# Use list_devices() to list all your input devices
    device = 3
    p = pyaudio.PyAudio()
    pyaudio.paDirectSound = 1
    stream = p.open(format = pyaudio.paInt16,
    channels = 1,
    rate = sample_rate,                   
    input = True,
    frames_per_buffer = chunk,
    input_device_index = device)

 
    mean = [12.0 for _ in range(GPIOLEN)]
    std = [1.5 for _ in range(GPIOLEN)]
    print "Starting, use Ctrl+C to stop"
    try:
        hc.initialize()
        #data = stream.read(chunk)
        frequency_limits = calculate_channel_frequency(min_frequency, max_frequency,0,0)
        print frequency_limits
        while True:
            
            data = stream.read(chunk)
            matrix = calculate_levels(data, sample_rate, frequency_limits)

            for i in range(0, GPIOLEN):
            # Calculate output pwm, where off is at 0.5 std below the mean
            # and full on is at 0.75 std above the mean.
                brightness = matrix[i] - mean[i] + 0.5 * std[i]
                #brightness = brightness / (1.25 * std[i])
                brightness = brightness / (2.2 * std[i])
                if brightness > 1.0:
                    brightness = 1.0
                if brightness < 0:
                    brightness = 0
                if not hc.is_pin_pwm(i):
                # If pin is on / off mode we'll turn on at 1/2 brightness
                    if (brightness > 0.5):
                        hc.turn_on_light(i, True)
                    else:
                        hc.turn_off_light(i, True)
                else:
                    hc.turn_on_light(i, True, brightness)

               
                print data    #<---- comment out to not see calculated values
 
    except KeyboardInterrupt:
        pass
    finally:
        print "\nStopping"
        hc.clean_up()
        
#ser.close()
def calculate_channel_frequency(min_frequency, max_frequency, custom_channel_mapping, custom_channel_frequencies):
    '''Calculate frequency values for each channel, taking into account custom settings.'''

    # How many channels do we need to calculate the frequency for
    if custom_channel_mapping != 0 and len(custom_channel_mapping) == GPIOLEN:
       
        channel_length = max(custom_channel_mapping)
    else:
        
        channel_length = GPIOLEN

   # logging.debug("Calculating frequencies for %d channels.", channel_length)
    octaves = (np.log(max_frequency / min_frequency)) / np.log(2)
   # logging.debug("octaves in selected frequency range ... %s", octaves)
    octaves_per_channel = octaves / channel_length
    frequency_limits = []
    frequency_store = []

    frequency_limits.append(min_frequency)
    if custom_channel_frequencies != 0 and (len(custom_channel_frequencies) >= channel_length + 1):
      
        frequency_limits = custom_channel_frequencies
    else:
      
        for i in range(1, GPIOLEN + 1):
            frequency_limits.append(frequency_limits[-1]
                                    * 10 ** (3 / (10 * (1 / octaves_per_channel))))
    for i in range(0, channel_length):
        frequency_store.append((frequency_limits[i], frequency_limits[i + 1]))
       

    # we have the frequencies now lets map them if custom mapping is defined
    if custom_channel_mapping != 0 and len(custom_channel_mapping) == GPIOLEN:
        frequency_map = []
        for i in range(0, GPIOLEN):
            mapped_channel = custom_channel_mapping[i] - 1
            mapped_frequency_set = frequency_store[mapped_channel]
            mapped_frequency_set_low = mapped_frequency_set[0]
            mapped_frequency_set_high = mapped_frequency_set[1]
            
            frequency_map.append(mapped_frequency_set)
        return frequency_map
    else:
        return frequency_store
    
if __name__ == '__main__':
#list_devices()
    extern_show()
